//! Collection helpers that mimic their Lodash counterparts.
//! 集合类工具，设计与 Lodash 中的同名方法保持一致。

use indexmap::{IndexMap, IndexSet};
use std::collections::HashSet;
use std::hash::Hash;

/// Splits a slice into equally sized chunks, like `_.chunk`.
/// 类似 `_.chunk`，把切片按固定大小切分为多个区块。
///
/// # Examples
/// ```
/// use price_utils::collection::chunk;
/// assert_eq!(chunk(&[1, 2, 3, 4, 5], 2), vec![vec![1, 2], vec![3, 4], vec![5]]);
/// ```
pub fn chunk<T: Clone>(
    input: &[T],
    size: usize,
) -> Vec<Vec<T>> {
    if size == 0 || input.is_empty() {
        return Vec::new();
    }

    input.chunks(size).map(|slice| slice.to_vec()).collect()
}

/// Removes `None` entries from a slice of options, like `_.compact`.
/// 类似 `_.compact`，移除 `Option` 切片中的 `None`。
pub fn compact<T: Clone>(input: &[Option<T>]) -> Vec<T> {
    input.iter().filter_map(|item| item.clone()).collect()
}

/// Flattens a slice-of-vectors by one level, like `_.flatten`.
/// 类似 `_.flatten`，把一层嵌套的向量打平。
pub fn flatten<T: Clone>(input: &[Vec<T>]) -> Vec<T> {
    input.iter().flat_map(|inner| inner.iter().cloned()).collect()
}

/// Returns items present in `minuend` but not `subtrahend`, like `_.difference`.
/// 类似 `_.difference`，找出在第一个集合出现但第二个集合没有的元素。
pub fn difference<T>(
    minuend: &[T],
    subtrahend: &[T],
) -> Vec<T>
where
    T: Eq + Hash + Clone,
{
    let sub: HashSet<_> = subtrahend.iter().collect();
    minuend.iter().filter(|item| !sub.contains(item)).cloned().collect()
}

/// Returns shared values across slices, like `_.intersection`.
/// 类似 `_.intersection`，返回两个集合共同拥有的值。
pub fn intersection<T>(
    left: &[T],
    right: &[T],
) -> Vec<T>
where
    T: Eq + Hash + Clone,
{
    let right_lookup: HashSet<_> = right.iter().collect();
    left.iter().filter(|item| right_lookup.contains(item)).cloned().collect()
}

/// Deduplicates values while preserving order, like `_.uniq`.
/// 类似 `_.uniq`，去重并保留原始顺序。
pub fn uniq<T>(items: &[T]) -> Vec<T>
where
    T: Eq + Hash + Clone,
{
    let mut seen = IndexSet::new();
    let mut deduped = Vec::with_capacity(items.len());

    for item in items {
        if seen.insert(item.clone()) {
            deduped.push(item.clone());
        }
    }

    deduped
}

/// Groups values using a key selector, like `_.groupBy`.
/// 类似 `_.groupBy`，按照键选择器对元素进行分组。
pub fn group_by<I, K, V, F>(
    iter: I,
    mut key_fn: F,
) -> IndexMap<K, Vec<V>>
where
    I: IntoIterator<Item = V>,
    K: Eq + Hash,
    F: FnMut(&V) -> K,
{
    let mut groups = IndexMap::new();
    for value in iter {
        let key = key_fn(&value);
        groups.entry(key).or_insert_with(Vec::new).push(value);
    }
    groups
}

/// Splits items into truthy/falsy partitions, like `_.partition`.
/// 类似 `_.partition`，把元素按谓词拆分为真/假两组。
pub fn partition<T, F>(
    iter: impl IntoIterator<Item = T>,
    mut predicate: F,
) -> (Vec<T>, Vec<T>)
where
    F: FnMut(&T) -> bool,
{
    let mut truthy = Vec::new();
    let mut falsy = Vec::new();

    for value in iter {
        if predicate(&value) {
            truthy.push(value);
        } else {
            falsy.push(value);
        }
    }

    (truthy, falsy)
}

/// Zips keys and values into an [`IndexMap`], dropping any unpaired tail elements.
/// 将键和值压缩成 `IndexMap`，若数量不匹配会忽略多余部分。
pub fn zip_to_map<K, V>(
    keys: impl IntoIterator<Item = K>,
    values: impl IntoIterator<Item = V>,
) -> IndexMap<K, V>
where
    K: Eq + Hash,
{
    keys.into_iter().zip(values).collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn chunking_handles_small_slices() {
        assert!(chunk::<u8>(&[], 3).is_empty());
        assert!(chunk::<u8>(&[1, 2, 3], 0).is_empty());
        assert_eq!(chunk(&[1, 2, 3, 4], 3), vec![vec![1, 2, 3], vec![4]]);
    }

    #[test]
    fn compact_removes_none() {
        let values = vec![Some(1), None, Some(2)];
        assert_eq!(compact(&values), vec![1, 2]);
    }

    #[test]
    fn difference_and_intersection_work() {
        let a = vec![1, 2, 3, 4];
        let b = vec![2, 4];
        assert_eq!(difference(&a, &b), vec![1, 3]);
        assert_eq!(intersection(&a, &b), vec![2, 4]);
    }

    #[test]
    fn unique_preserves_order() {
        let data = vec!["a", "b", "a", "c", "b"];
        assert_eq!(uniq(&data), vec!["a", "b", "c"]);
    }

    #[test]
    fn group_and_partition_behave_like_lodash() {
        let grouped = group_by(0..6, |value| value % 2 == 0);
        assert_eq!(grouped[&true], vec![0, 2, 4]);
        assert_eq!(grouped[&false], vec![1, 3, 5]);

        let (even, odd) = partition(0..4, |value| value % 2 == 0);
        assert_eq!(even, vec![0, 2]);
        assert_eq!(odd, vec![1, 3]);
    }

    #[test]
    fn zip_to_map_drops_unpaired_values() {
        let map = zip_to_map(["a", "b", "c"], [1, 2]);
        assert_eq!(map.get("a"), Some(&1));
        assert_eq!(map.get("b"), Some(&2));
        assert!(map.get("c").is_none());
    }
}
